/* FsCollection.java Copyright 2010 by Jo Pol
 *
 * This file is part of fs-thumbs.
 *
 * fs-thumbs  is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * fs-thumbs  is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with BobbinWork.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.fs_thumbs;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class FsCollection {
	
	private final Document document;
	private final String collectionId;
	private static DocumentBuilder documentBuilder;
	private static XPath xPath;

	public static class FsException extends Exception {

		private static final long serialVersionUID = 2776605620640456575L;

		private FsException(final String message, final Throwable cause) {
			super(message, cause);
		}
	}

	public class Waypoint {
		private final Element element;

		private Waypoint(final Element element) {
			this.element = element;
		}

		public String getId() {
			return element.getAttribute("id");
		}

		public String getName() {
			return element.getElementsByTagName("name").item(0).getTextContent();
		}

		public int getChildCount() {
			return getTheChildCount(element);
		}

		public boolean isLeaf() {
			return isALeaf(element);
		}
		
		public final String toString() {
			return getName();
		}
	}

	public class Image {

		private final String dgsNumber;
		private final String imageNumber;
		private final String url;
		private final int row;

		private Image(final String url, final int row) {
			this.url = url;
			this.row = row;
			dgsNumber = url.replaceAll(".*dgs:", "").replaceAll("\\..*", "");
			imageNumber = url.replace("/$dist", "").replaceAll(".*_", "");
		}

		public String getUrl() {
			return url;
		}

		public String getDgsNumber() {
			return dgsNumber;
		}

		public String getImageNumber() {
			return imageNumber;
		}

		public int getRow() {
			return row;
		}
	}

	public FsCollection getCollection(final String waypointId) throws IOException, FsException, FsException {
		return new FsCollection(new FsUrl(getCollectionId(),waypointId));
	}

	public FsCollection (final String url) throws IOException, FsException {
		this( getFsUrl(url));
	}
	
	private FsCollection(final FsUrl fsUrl) throws IOException, FsException, FsException {

		try {
			document = getDocumentBuilder().parse(fsUrl.toURL().openStream());
			collectionId = fsUrl.getCollectionId();
		} catch (final SAXException exception) {
			throw new FsException(exception.getMessage(), exception);
		} catch (final IOException exception) {
			throw exception;
		}
		if (subRoot() == null) {
			throw new IllegalArgumentException("illegal content in " + fsUrl.toString());
		}
	}

	private static FsUrl getFsUrl(final String xmlUrl) throws IOException, FsException {
		final FsUrl fsUrl;
		try {
			fsUrl = new FsUrl(xmlUrl);
		} catch (final MalformedURLException exception) {
			throw new IllegalArgumentException(xmlUrl + Main.NL + exception.getMessage(), exception);
		}
		return fsUrl;
	}

	public List<Waypoint> getCrumbs() throws FsException {
		return buildWaypoints(getWaypoints("crumbs"));
	}

	public List<Waypoint> getChildReferences() throws FsException {
		return buildWaypoints(getWaypoints("children"));
	}

	public boolean isLeaf() {
		return isALeaf(subRoot());
	}

	public int getChildCount() {
		return getTheChildCount(subRoot());
	}

	public String getName() {
		return subRoot().getElementsByTagName("name").item(0).getTextContent();
	}

	public List<Image> getImages() throws FsException {

		final List<Image> images = new ArrayList<Image>();
		final NodeList nodes = getNodes("//*[@href]");
		for (int i = 0; i < nodes.getLength(); i++) {
			final Element element = (Element) nodes.item(i);
			images.add(new Image(element.getAttribute("href"),i+1));
		}
		return images;
	}

	public int getGrandChildCount() throws FsException {

		// don't mistake images for children
		if (isALeaf(subRoot()))
			return 0;

		int total = 0;
		final NodeList nodes = getNodes("//*[@row]");
		for (int i = 0; i < nodes.getLength(); i++) {
			total += getTheChildCount((Element) nodes.item(i));
		}
		return total;
	}

	private NodeList getWaypoints(final String containerTag) throws FsException {

		final Element subRoot = subRoot();
		if (subRoot == null)
			return null;
		final Element element = (Element) subRoot.getElementsByTagName(containerTag).item(0);
		if (element == null)
			return null;
		return element.getElementsByTagName("waypoint");
	}

	private List<Waypoint> buildWaypoints(final NodeList nodes) {
		final List<Waypoint> waypoints = new ArrayList<Waypoint>();
		if (nodes == null)
			return waypoints;
		for (int i = 0; i < nodes.getLength(); i++) {
			waypoints.add(new Waypoint((Element) nodes.item(i)));
		}
		return waypoints;
	}

	private Element subRoot() {
		return (Element) document.getDocumentElement().getElementsByTagName("waypoint").item(0);
	}

	private boolean isALeaf(final Element waypoint) {
		// without the article in the name we have a name collision with Crumb
		return waypoint.getAttribute("leaf").toLowerCase().equals("true");
	}


	public String getWaypointId() {
		return subRoot().getAttribute("id");
	}

	private int getTheChildCount(final Element waypoint) {
		// without the article in the name we have a name collision with Crumb
		return new Integer(waypoint.getAttribute("childCount"));
	}

	private NodeList getNodes(final String xpathExpression) throws FsException {
		try {
			return (NodeList) getXPath().evaluate(xpathExpression, document, XPathConstants.NODESET);
		} catch (final XPathExpressionException exception) {
			throw new FsException(null, exception);
		}
	}

	private DocumentBuilder getDocumentBuilder() throws FsException {

		if (documentBuilder != null)
			return documentBuilder;
		final DocumentBuilderFactory newInstance = DocumentBuilderFactory.newInstance();
		try {
			documentBuilder = newInstance.newDocumentBuilder();
		} catch (final ParserConfigurationException exception) {
			throw new FsException(exception.getMessage(), exception);
		}
		return documentBuilder;
	}

	private XPath getXPath() {
		if (xPath != null)
			return xPath;
		xPath = XPathFactory.newInstance().newXPath();
		return xPath;
	}

	public String getCollectionId() {
		return collectionId;
	}
}
